/************************************************************************
 *   IRC - Internet Relay Chat, ircd/list.c
 *   Copyright (C) 1990 Jarkko Oikarinen and
 *                      University of Oulu, Finland
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 1, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "h.h"
#ifdef	DBMALLOC
#include "malloc.h"
#endif
void free_link PROTO ((Link *));
Link *make_link PROTO (());

static struct liststats
{
    int inuse;
    int free;
}
listc[8];

#define	LC_CLOC	0
#define	LC_CREM 1
#define	LC_SERV	2
#define	LC_LINK	3
#define	LC_USER	4
#define	LC_CONF	5
#define	LC_CLAS	6
#define	LC_DBUF	7

void outofmemory ();

static aClient *clofree = NULL;
static aClient *crefree = NULL;
static aClass *clfree = NULL;
static aConfItem *cofree = NULL;
static anUser *ufree = NULL;
static Link *lfree = NULL;
static aServer *sfree = NULL;

int numclients = 0;

void initlists ()
{
    bzero (listc, sizeof (struct liststats) * 7);
}

void outofmemory ()
{
    Debug ((DEBUG_FATAL, "Out of memory: restarting server..."));
    restart ("Out of Memory");
}


/*
   ** Create a new aClient structure and set it to initial state.
   **
   **   from == NULL,   create local client (a client connected
   **           to a socket).
   **
   **   from,   create remote client (behind a socket
   **           associated with the client defined by
   **           'from'). ('from' is a local client!!).
 */
aClient *make_client (from)
     aClient *from;
{
    aClient *cptr = NULL;
    unsigned size = CLIENT_REMOTE_SIZE;

    /*
     * Check freelists first to see if we can grab a client without
     * having to call malloc.
     */
    if (!from) {
	size = CLIENT_LOCAL_SIZE;
	if ((cptr = clofree)) {
	    clofree = cptr->next;
	    listc[LC_CLOC].free--;
	    Debug ((DEBUG_LIST, "make_client(%#x) = %#x", from, cptr));
	}
    }
    else if ((cptr = crefree)) {
	crefree = cptr->next;
	listc[LC_CREM].free--;
	Debug ((DEBUG_LIST, "make_client(%#x) = %#x", from, cptr));
    }
    if (!cptr) {
	if (!(cptr = (aClient *) MyMalloc (size)))
	    outofmemory ();
	else {
	    if (size == CLIENT_LOCAL_SIZE)
		listc[LC_CLOC].inuse++;
	    else
		listc[LC_CREM].inuse++;
	}
    }
    bzero ((char *) cptr, (int) size);

    /* Note:  structure is zero (calloc) */
    cptr->from = from ? from : cptr;	/* 'from' of local client is self! */
    cptr->next = NULL;		  /* For machines with NON-ZERO NULL pointers >;) */
    cptr->prev = NULL;
    cptr->hnext = NULL;
    cptr->user = NULL;
    cptr->serv = NULL;
    cptr->status = STAT_UNKNOWN;
    cptr->fd = -1;
    (void) strcpy (cptr->username, "unknown");
    if (size == CLIENT_LOCAL_SIZE) {
	cptr->since = cptr->lasttime =
	    cptr->lastnick = cptr->firsttime = time (NULL);
	cptr->confs = NULL;
	cptr->sockhost[0] = '\0';
	cptr->buffer[0] = '\0';
    }
    return (cptr);
}


checksanity ()
{
    register aClient *c;
    register anUser *u;
    register aServer *s;

    for (c = client; c; c = c->next)
#ifdef	LIST_DEBUG
	if ((u = c->user) && (u->bcptr != c))
	    dumpcore ("c %#x u %#x b %#x", c, u, u->bcptr);
	else if ((s = c->serv) && s->bcptr != c)
	    dumpcore ("c %#x s %#x b %#x", c, s, s->bcptr);
	else
#endif
	if (u && u->refcnt <= 0)
	    dumpcore ("c %#x u %#x r %d", c, u, u->refcnt);
}


void free_client (cptr)
     aClient *cptr;
{
    Debug ((DEBUG_LIST, "free_client(%#x) %d", cptr, cptr->fd));
    if (cptr->fd != -1) {
	bzero ((char *) cptr, CLIENT_LOCAL_SIZE);
	listc[LC_CLOC].free++;
	cptr->next = clofree;
	clofree = cptr;
    }
    else {
	bzero ((char *) cptr, CLIENT_REMOTE_SIZE);
	listc[LC_CREM].free++;
	cptr->next = crefree;
	crefree = cptr;
    }
}

/*
   ** 'make_user' add's an User information block to a client
   ** if it was not previously allocated.
 */
anUser *make_user (cptr)
     aClient *cptr;
{
    anUser *user;
    char c;

    user = cptr->user;
    if (!user)
	if ((user = ufree)) {
	    ufree = user->nextu;
	    listc[LC_USER].free--;
	    c = '-';
	}
    if (!user) {
	user = (anUser *) MyMalloc (sizeof (anUser));
	listc[LC_USER].inuse++;
	c = '=';
    }
    cptr->user = user;
    user->nextu = NULL;
    user->away = NULL;
    user->refcnt = 1;
    user->joined = 0;
    user->channel = NULL;
    user->invited = NULL;
    user->silence = NULL;
    Debug ((DEBUG_LIST, "make_user(%#x) %c %#x %d",
	    cptr, c, user, user->refcnt));
    user->bcptr = cptr;
    return user;
}

aServer *make_server (cptr)
     aClient *cptr;
{
    aServer *serv = cptr->serv;
    char c;

    if (!serv)
	if ((serv = sfree)) {
	    sfree = serv->nexts;
	    listc[LC_SERV].free--;
	    c = '-';
	}
    if (!serv) {
	serv = (aServer *) MyMalloc (sizeof (aServer));
	listc[LC_SERV].inuse++;
	c = '=';
    }
    serv->user = NULL;
    serv->nexts = NULL;
    *serv->by = '\0';
    *serv->up = '\0';
    cptr->serv = serv;
#ifdef	LIST_DEBUG
    serv->bcptr = cptr;
#endif
    Debug ((DEBUG_LIST, "make_server(%#x) %c %#x", cptr, c, serv));
    return cptr->serv;
}

/*
   ** free_user
   **   Decrease user reference count by one and realease block,
   **   if count reaches 0
 */
void free_user (user, cptr)
     anUser *user;
     aClient *cptr;
{
    if (cptr && user->bcptr && (user->bcptr != cptr)) {
	dumpcore ("user %#x bcptr %#x cptr %#x", user, user->bcptr, cptr);
	exit (0);
    }
    user->bcptr = cptr;
    user->refcnt--;
    Debug ((DEBUG_LIST, "free_user(%#x,%#x) %d", user, cptr, user->refcnt));
    if (user->refcnt <= 0) {
	if (user->away)
	    (void) MyFree ((char *) user->away);
	bzero ((char *) user, sizeof (*user));
	user->nextu = ufree;
	ufree = user;
	listc[LC_USER].free++;
    }
}

/*
 * taken the code from ExitOneClient() for this and placed it here.
 * - avalon
 */
void remove_client_from_list (cptr)
     aClient *cptr;
{
    checklist ();
    if (cptr->prev)
	cptr->prev->next = cptr->next;
    else {
	client = cptr->next;
	client->prev = NULL;
    }
    if (cptr->next)
	cptr->next->prev = cptr->prev;
    if (cptr->user) {
	add_history (cptr);
	off_history (cptr);
	(void) free_user (cptr->user, cptr);
    }
    if (cptr->serv) {
	if (cptr->serv->user)
	    free_user (cptr->serv->user, cptr);
	listc[LC_SERV].free++;
	cptr->serv->nexts = sfree;
	cptr->serv->bcptr = NULL;
	sfree = cptr->serv;
    }
    free_client (cptr);
    return;
}

/*
 * although only a small routine, it appears in a number of places
 * as a collection of a few lines...functions like this *should* be
 * in this file, shouldnt they ?  after all, this is list.c, isnt it ?
 * -avalon
 */
void add_client_to_list (cptr)
     aClient *cptr;
{
    /*
     * since we always insert new clients to the top of the list,
     * this should mean the "me" is the bottom most item in the list.
     */
    cptr->next = client;
    client = cptr;
    if (cptr->next)
	cptr->next->prev = cptr;
    return;
}

/*
 * Look for ptr in the linked listed pointed to by link.
 */
Link *find_user_link (lp, ptr)
     Link *lp;
     aClient *ptr;
{
    while (lp && ptr) {
	if (lp->value.cptr == ptr)
	    return (lp);
	lp = lp->next;
    }
    return NULL;
}

Link *make_link ()
{
    Link *lp;
    char c;

    if ((lp = lfree)) {
	lfree = lp->next;
	listc[LC_LINK].free--;
	c = '-';
    }
    else {
	lp = (Link *) MyMalloc (sizeof (Link) * 3);
	bzero ((char *) lp + 1, sizeof (Link) * 2);
	lp->next = lp + 1;
	lp->next->next = lp + 2;
	lp->next->next->next = lfree;
	lfree = lp->next;
	listc[LC_LINK].inuse += 3;
	listc[LC_LINK].free += 2;
	c = '=';
    }
    Debug ((DEBUG_LIST, "make_link() %c %#x", c, lp));
    return lp;
}

void free_link (lp)
     Link *lp;
{
    bzero ((char *) lp, sizeof (*lp));
    lp->next = lfree;
    lfree = lp;
    listc[LC_LINK].free++;
    Debug ((DEBUG_LIST, "free_link(%#x)", lp));
}


aClass *make_class ()
{
    aClass *tmp;

    if ((tmp = clfree)) {
	listc[LC_CLAS].free--;
	clfree = tmp->next;
	Debug ((DEBUG_LIST, "make_class() - %#x", tmp));
    }
    else {
	tmp = (aClass *) MyMalloc (sizeof (aClass));
	listc[LC_CLAS].inuse++;
	Debug ((DEBUG_LIST, "make_class() = %#x", tmp));
    }
    return tmp;
}

void free_class (tmp)
     aClass *tmp;
{
    bzero ((char *) tmp, sizeof (*tmp));
    tmp->next = clfree;
    clfree = tmp;
    listc[LC_CLAS].free++;
    Debug ((DEBUG_LIST, "free_class(%#x)", tmp));
}

aConfItem *make_conf ()
{
    aConfItem *aconf;
    char c;

    if ((aconf = cofree)) {
	cofree = aconf->next;
	listc[LC_CONF].free--;
	c = '-';
    }
    else {
	aconf = (struct ConfItem *) MyMalloc (sizeof (aConfItem));
	listc[LC_CONF].inuse++;
	c = '=';
	bzero ((char *) aconf, sizeof (*aconf));
    }
    aconf->next = NULL;
    aconf->host = aconf->passwd = aconf->name = NULL;
    aconf->status = CONF_ILLEGAL;
    Class (aconf) = 0;
    Debug ((DEBUG_LIST, "make_conf() %c %#x", c, aconf));
    return (aconf);
}

void free_conf (aconf)
     aConfItem *aconf;
{
    MyFree (aconf->host);
    if (aconf->passwd)
	bzero (aconf->passwd, strlen (aconf->passwd));
    MyFree (aconf->passwd);
    MyFree (aconf->name);
    bzero ((char *) aconf, sizeof (*aconf));
    aconf->next = cofree;
    cofree = aconf;
    Debug ((DEBUG_LIST, "free_conf(%#x)", aconf));
    listc[LC_CONF].free++;
    return;
}

void send_listinfo (cptr, name)
     aClient *cptr;
     char *name;
{
    static char *labels[] = { "Local", "Remote", "Servs", "Links",
	"Users", "Confs", "Classes", "dbufs"
    };
    static int sizes[] = { CLIENT_LOCAL_SIZE, CLIENT_REMOTE_SIZE,
	sizeof (aServer), sizeof (Link),
	sizeof (anUser), sizeof (aConfItem),
	sizeof (aClass), sizeof (dbufbuf)
    };

    struct liststats *ls = listc;
    int inuse = 0, mem = 0, tmp = 0, i;

    listc[LC_DBUF].inuse = dbufblocks;
    listc[LC_DBUF].free = dbufblocks - dbufalloc;
    for (i = 0; i < 8; i++, ls++) {
	tmp = sizes[i] * ls->inuse;
	sendto_one (cptr, ":%s NOTICE %s :%s: inuse: %d(%d) free: %d",
		    me.name, cptr->name, labels[i], ls->inuse, tmp, ls->free);
	inuse += ls->inuse;
	mem += tmp;
    }

    sendto_one (cptr, ":%s NOTICE %s :Totals: inuse %d %d",
		me.name, name, inuse, mem);
}
